package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {


    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;

    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired  //先按类型注入 再按名称注入
    private Executor threadPoolTaskExecutor;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    @Autowired
    private RedissonClient redissonClient;

    //@Autowired
    //@Qualifier("threadPoolTaskExecutor")
    //private Executor executor;

    //@Resource
    //private Executor threadPoolTaskExecutor;

    /**
     * 将指定专辑导入索引库
     *
     * @param albumId
     */
    @Override
    public void upperAlbum(Long albumId) {
        log.info("主线程:{}，专辑索引库对象", Thread.currentThread().getName());
        //1.创建索引库对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        //2.封装专辑索引库基本信息
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //2.1 远程调用专辑服务根据专辑ID查询专辑信息包含标签列表
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            log.info("子线程:{}，执行查询专辑", Thread.currentThread().getName());
            Assert.notNull(albumInfo, "专辑:{}不存在", albumId);
            //2.2 为索引库对象中属性赋值
            BeanUtil.copyProperties(albumInfo, albumInfoIndex);
            //2.3 专辑可能存在标签列表，需要单独处理
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (CollUtil.isNotEmpty(albumAttributeValueVoList)) {
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList
                        .stream()
                        .map(albumAttributeValue -> BeanUtil.copyProperties(albumAttributeValue, AttributeValueIndex.class))
                        .collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
            return albumInfo;
        }, threadPoolTaskExecutor);

        ////3.封装专辑索引库分类信息
        CompletableFuture<Void> categoryCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            log.info("子线程:{}，执行查询分类", Thread.currentThread().getName());
            BaseCategoryView categoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
            Assert.notNull(categoryView, "专辑分类:{}不存在", albumInfo.getCategory3Id());
            albumInfoIndex.setCategory1Id(categoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(categoryView.getCategory2Id());
        }, threadPoolTaskExecutor);
        //
        ////4.封装专辑索引库主播信息
        CompletableFuture<Void> userCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            log.info("子线程:{}，执行查询用户", Thread.currentThread().getName());
            UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData();
            Assert.notNull(userInfoVo, "用户:{}不存在", albumInfo.getUserId());
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolTaskExecutor);
        //
        ////5.封装专辑索引库统计信息 暂时采用随机生成方式 TODO 上线后需要实际查询统计数值
        CompletableFuture<Void> statCompletableFuture = CompletableFuture.runAsync(() -> {
            //5.1 随机产生四个数值对应 播放数，订阅数，购买数，评论数
            int num1 = RandomUtil.randomInt(1000, 5000);
            int num2 = RandomUtil.randomInt(1000, 1500);
            int num3 = RandomUtil.randomInt(100, 1000);
            int num4 = RandomUtil.randomInt(500, 1000);
            albumInfoIndex.setPlayStatNum(num1);
            albumInfoIndex.setSubscribeStatNum(num2);
            albumInfoIndex.setBuyStatNum(num3);
            albumInfoIndex.setCommentStatNum(num4);
            //5.2 基于四个数值计算专辑热度值  热度值+=统计数*系数
            BigDecimal hotScore = BigDecimal.valueOf(0.1).multiply(BigDecimal.valueOf(num1))
                    .add(BigDecimal.valueOf(0.2).multiply(BigDecimal.valueOf(num2)))
                    .add(BigDecimal.valueOf(0.3).multiply(BigDecimal.valueOf(num3)))
                    .add(BigDecimal.valueOf(0.4).multiply(BigDecimal.valueOf(num4)));
            albumInfoIndex.setHotScore(hotScore.doubleValue());
        }, threadPoolTaskExecutor);


        //6.组合异步任务，确保所有任务都执行完毕
        CompletableFuture
                .allOf(
                        albumInfoCompletableFuture,
                        statCompletableFuture,
                        categoryCompletableFuture,
                        userCompletableFuture
                ).join();

        //7.将专辑索引库信息保存到索引库
        albumInfoIndexRepository.save(albumInfoIndex);

        //8.保存专辑名称到提词索引库
        this.saveSuggest(albumInfoIndex);

        //9.将上架专辑ID存入布隆过滤器
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
        bloomFilter.add(albumId);
    }

    /**
     * 下架索引库专辑
     *
     * @param albumId
     */
    @Override
    public void lowerAlbum(Long albumId) {
        albumInfoIndexRepository.deleteById(albumId);
        suggestIndexRepository.deleteById(albumId);
    }

    /**
     * 多条件条件分页检索
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        try {
            //一、构建检索请求对象
            SearchRequest searchRequest = this.buildDSL(albumIndexQuery);
            System.err.println("本次检索DSL：");
            System.err.println(searchRequest.toString());
            //二、调用ES客户端执行检索
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
            //三、解析封装ES响应结果
            return this.parseResult(searchResponse, albumIndexQuery);
        } catch (Exception e) {
            log.error("[检索服务]专辑检索异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    private static final String INDEX_NAME = "albuminfo";
    private static final String SUGGEST_INDEX_NAME = "suggestinfo";

    /**
     * 基于检索条件封装完整检索请求对象
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public SearchRequest buildDSL(AlbumIndexQuery albumIndexQuery) {
        //1.创建检索请求 构建器对象->封装请求路径索引名称以及完整请求体参数
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(INDEX_NAME);
        //2.封装请求体参数 query。通过布尔查询封装 三大条件：1.关键字  2.分类  3.标签
        //2.1 创建封装三大条件bool查询对象
        BoolQuery.Builder allConditionBoolQueryBuilder = new BoolQuery.Builder();
        //2.2 设置关键字条件查询
        String keyword = albumIndexQuery.getKeyword();
        if (StringUtils.isNotBlank(keyword)) {
            allConditionBoolQueryBuilder.must(m -> m.bool(
                    b -> b.should(s -> s.match(m1 -> m1.field("albumTitle").query(keyword)))
                            .should(s -> s.match(m1 -> m1.field("albumIntro").query(keyword)))
                            .should(s -> s.term(t -> t.field("announcerName").value(keyword)))
            ));
        }
        //2.3 设置分类条件过滤
        if (albumIndexQuery.getCategory1Id() != null) {
            allConditionBoolQueryBuilder.filter(f -> f.term(t -> t.field("category1Id").value(albumIndexQuery.getCategory1Id())));
        }
        if (albumIndexQuery.getCategory2Id() != null) {
            allConditionBoolQueryBuilder.filter(f -> f.term(t -> t.field("category2Id").value(albumIndexQuery.getCategory2Id())));
        }
        if (albumIndexQuery.getCategory3Id() != null) {
            allConditionBoolQueryBuilder.filter(f -> f.term(t -> t.field("category3Id").value(albumIndexQuery.getCategory3Id())));
        }

        //2.4 设置标签条件过滤 可能有多组标签 每一组标签条件产生一个嵌套查询
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (CollUtil.isNotEmpty(attributeList)) {
            //2.4.1 遍历标签列表 每遍历一次设置过滤Nested查询
            for (String attribute : attributeList) {
                //标签条件=标签ID:标签值ID
                String[] split = attribute.split(":");
                if (split != null && split.length == 2) {
                    allConditionBoolQueryBuilder.filter(f -> f.nested(
                            n -> n.path("attributeValueIndexList")
                                    .query(q -> q.bool(
                                            b -> b.must(m -> m.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                                                    .must(m -> m.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))
                                    ))
                    ));
                }
            }
        }
        //2.5 将三大条件的布尔查询对象设置到"query"中
        builder.query(allConditionBoolQueryBuilder.build()._toQuery());
        //2.2 封装分页条件 from=起始文档offset size=页大小
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        Integer from = (pageNo - 1) * pageSize;
        builder.from(from).size(pageSize);

        //2.3 封装排序条件 sort
        //2.3.1 获取排序条件 形式=>字段标识1/2/3:asc/desc   1：综合排序  2：发布时间  3：播放量
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isNotBlank(order)) {
            //2.3.2 如果有需要自定义排序，才设置请求参数:sort
            String[] split = order.split(":");
            if (split != null && split.length == 2) {
                //2.3.3 得到排序字段 跟 排序方式
                String sortField = "";
                switch (split[0]) {
                    case "1":
                        sortField = "hotScore";
                        break;
                    case "2":
                        sortField = "playStatNum";
                        break;
                    case "3":
                        sortField = "createTime";
                        break;
                }
                String finalSortField = sortField;
                builder.sort(s -> s.field(f -> f.field(finalSortField).order(split[1].equals("desc") ? SortOrder.Desc : SortOrder.Asc)));
            }
        }
        //2.4 封装高亮条件 highlight
        if (StringUtils.isNotBlank(keyword)) {
            builder.highlight(h -> h.fields("albumTitle", f -> f.preTags("<font style='color:red'>").postTags("</font>")));
        }
        //2.5 设置文档响应字段
        builder.source(s -> s.filter(f -> f.excludes("attributeValueIndexList", "hotScore", "commentStatNum", "buyStatNum", "subscribeStatNum", "announcerName")));
        //3.基于构建器对象产生检索请求对象
        return builder.build();
    }

    /**
     * 解析ES响应结果封装自定义VO
     *
     * @param searchResponse
     * @param albumIndexQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo parseResult(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery albumIndexQuery) {
        //1.创建响应VO对象
        AlbumSearchResponseVo vo = new AlbumSearchResponseVo();
        //2.封装VO中分页信息
        //2.1 从检索条件对象中获取页码、页大小
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        //2.2 从响应结果对象中获取总记录数，计算总页数
        HitsMetadata<AlbumInfoIndex> hits = searchResponse.hits();
        long total = hits.total().value();
        long totalPage = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;

        vo.setPageNo(pageNo);
        vo.setPageSize(pageSize);
        vo.setTotal(total);
        vo.setTotalPages(totalPage);
        //3.封装VO中业务数据集合
        //3.1 获取检索结果中hits对象中包含hits数组
        List<Hit<AlbumInfoIndex>> hitList = hits.hits();

        //3.2 遍历hits数组，得到文档对象中_source对应文档对象
        if (CollUtil.isNotEmpty(hitList)) {
            List<AlbumInfoIndexVo> albumInfoVoList = hitList
                    .stream()
                    .map(hit -> {
                        AlbumInfoIndex albumInfoIndex = hit.source();
                        //3.3 如果有高亮字段，则替换文档对象中对应字段的值
                        Map<String, List<String>> map = hit.highlight();
                        if (CollUtil.isNotEmpty(map)) {
                            List<String> list = map.get("albumTitle");
                            String highLigthText = list.get(0);
                            albumInfoIndex.setAlbumTitle(highLigthText);
                        }
                        return BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class);
                    }).collect(Collectors.toList());
            vo.setList(albumInfoVoList);
        }
        //4.响应vo
        return vo;
    }

    /**
     * 得到置顶三级分类热门专辑TOP6列表
     *
     * @param category1Id
     * @return
     */
    @Override
    public List<Map<String, Object>> getBaseCategoryListByCategory1Id(Long category1Id) {
        try {
            //1.根据1级分类ID获取置顶的7个三级分类列表,获取7个三级分类ID
            //1.1 远程调用专辑服务获取置顶三级分类列表
            List<BaseCategory3> baseCategory3List = albumFeignClient.findTopBaseCategory3(category1Id).getData();
            Assert.notNull(baseCategory3List, "根据一级分类ID获取置顶的7个三级分类列表失败");
            //1.2 将三级分类对象处理得到ES检索需要的FiledValue类型
            List<FieldValue> fieldValueList = baseCategory3List.stream()
                    .map(baseCategory3 -> FieldValue.of(baseCategory3.getId()))
                    .collect(Collectors.toList());

            //1.3 为了封装结果中得到三级分类对象，将三级分类列表转为Map<三级分类ID，三级分类对象>
            Map<Long, BaseCategory3> category3Map =
                    baseCategory3List.stream().collect(Collectors.toMap(c3 -> c3.getId(), c3 -> c3));

            //2.采用多关键字精确查询+聚合执行检索ES
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                    s -> s.index(INDEX_NAME)
                            .size(0)
                            .query(q -> q.terms(t -> t.field("category3Id").terms(t1 -> t1.value(fieldValueList))))
                            .aggregations(
                                    "category_agg", a -> a.terms(t -> t.field("category3Id").size(7))
                                            .aggregations("top6", a1 -> a1.topHits(t -> t.size(6).sort(s1 -> s1.field(f -> f.field("hotScore").order(SortOrder.Desc))).source(s1 -> s1.filter(f -> f.excludes("attributeValueIndexList")))))
                            ),
                    AlbumInfoIndex.class
            );
            //3.解析响应结果封装置顶分类热门专辑列表
            //3.1 获取分类聚合结果对象
            LongTermsAggregate categoryAgg = searchResponse.aggregations().get("category_agg").lterms();
            //3.2 获取分类聚合结果对象中的桶集合
            List<LongTermsBucket> cagegoryTermsBucketList = categoryAgg.buckets().array();
            if (CollUtil.isNotEmpty(cagegoryTermsBucketList)) {
                //3.3 遍历桶集合，每遍历一个桶，封装一个置顶分类热门专辑Map对象
                List<Map<String, Object>> list = cagegoryTermsBucketList
                        .stream()
                        .map(categoryBucket -> {
                            //3.4 封装置顶分类热门专辑Map
                            Map<String, Object> map = new HashMap<>();
                            //3.4.1 处理分类信息：获取桶中的三级分类ID，得到三级分类对象
                            map.put("baseCategory3", category3Map.get(categoryBucket.key()));
                            //3.4.2 处理热门专辑信息：获取桶中的热门专辑Top6聚合结果，得到热门专辑对象集合
                            List<AlbumInfoIndex> top6AlbumList = categoryBucket.aggregations().get("top6").topHits().hits().hits()
                                    .stream()
                                    .map(hit -> {
                                        String albumInfoIndexStr = hit.source().toString();
                                        AlbumInfoIndex albumInfoIndex = JSON.parseObject(albumInfoIndexStr, AlbumInfoIndex.class);
                                        return albumInfoIndex;
                                    }).collect(Collectors.toList());
                            map.put("list", top6AlbumList);
                            return map;
                        }).collect(Collectors.toList());
                return list;
            }
            return null;
        } catch (IOException e) {
            log.error("[搜索服务]首页置顶分类热门专辑检索异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 将专辑名称存入提词索引库
     *
     * @param albumInfoIndex
     */
    @Override
    public void saveSuggest(AlbumInfoIndex albumInfoIndex) {
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumInfoIndex.getId());
        String albumTitle = albumInfoIndex.getAlbumTitle();
        suggestIndex.setTitle(albumTitle);
        //存入汉字
        suggestIndex.setKeyword(new Completion(new String[]{albumTitle}));
        //将汉字转为汉语拼音
        String pinyin = PinyinUtil.getPinyin(albumTitle, "");
        suggestIndex.setKeywordPinyin(new Completion(new String[]{pinyin}));
        //获取拼音首字母
        String firstLetter = PinyinUtil.getFirstLetter(albumTitle, "");
        suggestIndex.setKeywordSequence(new Completion(new String[]{firstLetter}));
        suggestIndexRepository.save(suggestIndex);
    }

    /**
     * 根据用户已录入字符返回提示词列表,自动补全效果
     *
     * @param keyword
     * @return
     */
    @Override
    public List<String> completeSuggest(String keyword) {
        try {
            //1.采用自动补全API尝试分别查询：汉字、拼音、首字母自动补全字段
            SearchResponse<SuggestIndex> suggestIndexSearchResponse = elasticsearchClient.search(
                    s -> s.index(SUGGEST_INDEX_NAME)
                            .suggest(s1 -> s1.suggesters(
                                                    "letter-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordSequence").size(10).skipDuplicates(true))
                                            ).suggesters("pinyin-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordPinyin").size(10).skipDuplicates(true).fuzzy(f -> f.fuzziness("2").minLength(4))))
                                            .suggesters("keyword-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keyword").size(10).skipDuplicates(true).fuzzy(f -> f.fuzziness("2").minLength(4))))
                            )
                    ,
                    SuggestIndex.class
            );
            //2.解析ES自动补全结果,将结果放入HashSet去重
            Set<String> hashSet = new HashSet<>();
            hashSet.addAll(this.parseSuggestResult(suggestIndexSearchResponse, "letter-suggest"));
            hashSet.addAll(this.parseSuggestResult(suggestIndexSearchResponse, "pinyin-suggest"));
            hashSet.addAll(this.parseSuggestResult(suggestIndexSearchResponse, "keyword-suggest"));

            //3.如果返回自动补全结果集合长度 小于10个，采用全文检索：检索专辑标题即可，补全到10个
            if (hashSet.size() < 10) {
                SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                        s -> s.index(INDEX_NAME)
                                .size(10)
                                .query(q -> q.match(m -> m.field("albumTitle").query(keyword))
                                ), AlbumInfoIndex.class);
                List<Hit<AlbumInfoIndex>> hitList = searchResponse.hits().hits();
                if (CollUtil.isNotEmpty(hitList)) {
                    for (Hit<AlbumInfoIndex> hit : hitList) {
                        AlbumInfoIndex source = hit.source();
                        hashSet.add(source.getAlbumTitle());
                        if (hashSet.size() >= 10) {
                            break;
                        }
                    }
                }
            }

            //4.如果返回自动补全结果集合长度 大于等于10个，则截取10个返回
            if (hashSet.size() >= 10) {
                return new ArrayList<>(hashSet).subList(0, 10);
            }
            return new ArrayList<>(hashSet);
        } catch (IOException e) {
            log.error("[搜索服务]自动补全检索异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析建议词结果
     *
     * @param suggestIndexSearchResponse ES响应对象
     * @param suggestName                自定义名称
     * @return
     */
    @Override
    public Collection<String> parseSuggestResult(SearchResponse<SuggestIndex> suggestIndexSearchResponse, String suggestName) {
        List<String> list = new ArrayList<>();
        List<Suggestion<SuggestIndex>> suggestions = suggestIndexSearchResponse.suggest().get(suggestName);
        for (Suggestion<SuggestIndex> suggestion : suggestions) {
            for (CompletionSuggestOption<SuggestIndex> option : suggestion.completion().options()) {
                SuggestIndex suggestIndex = option.source();
                list.add(suggestIndex.getTitle());
            }
        }
        return list;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 手动更新Redis中小时榜数据
     */
    @Override
    public void updateLatelyAlbumRanking() {
        try {
            //1.远程调用专辑服务获取所有1级分类ID列表
            List<BaseCategory1> baseCategory1List = albumFeignClient.getAllCategory1().getData();
            if (CollUtil.isNotEmpty(baseCategory1List)) {
                List<Long> category1IdList = baseCategory1List.stream().map(BaseCategory1::getId).collect(Collectors.toList());
                //2.处理不同1级分类Redis中小时榜数据
                for (Long category1Id : category1IdList) {
                    //2.1 构建Redis中每个1级分类Hash小时榜数据Key 创建绑定操作hash对象
                    String redisKey = RedisConstant.RANKING_KEY_PREFIX + category1Id;
                    BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(redisKey);
                    //2.2 遍历1级分类列表，内部按照5种不同排序方式遍历
                    String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
                    for (String rankingDimension : rankingDimensionArray) {
                        //2.2.1 根据1级分类ID+排序方式+获取TOP20检索ES
                        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                                s -> s.index(INDEX_NAME).size(20)
                                        .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                                        .sort(s1 -> s1.field(f -> f.field(rankingDimension).order(SortOrder.Desc)))
                                        .source(s1 -> s1.filter(f -> f.excludes("attributeValueIndexList", "hotScore", "commentStatNum", "buyStatNum", "subscribeStatNum", "announcerName")))
                                ,
                                AlbumInfoIndex.class
                        );
                        //2.2.2 解析ES响应结果将解析结果存入Redis
                        List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
                        if(CollUtil.isNotEmpty(hits)){
                            List<AlbumInfoIndex> top20List =
                                    hits.stream().map(hit -> hit.source()).collect(Collectors.toList());
                            hashOps.put(rankingDimension, top20List);
                        }
                    }

                }
            }
        } catch (IOException e) {
            log.error("[搜索服务]手动更新Redis中小时榜数据异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension) {
        String redisKey = RedisConstant.RANKING_KEY_PREFIX + category1Id;
        BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(redisKey);
        List<AlbumInfoIndex> list = hashOps.get(dimension);
        if(CollUtil.isNotEmpty(list)){
            List<AlbumInfoIndexVo> list1 = list.stream().map(albumInfoIndex -> BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class))
                    .collect(Collectors.toList());
            return list1;
        }
        return null;
    }

    /**
     * 更新专辑统计数据
     *
     * @param trackStatMqVo
     */
    @Override
    public void updateAlbumStat(TrackStatMqVo trackStatMqVo) {
        try {
            Long albumId = trackStatMqVo.getAlbumId();
            String incrementField = "";
            if(SystemConstant.TRACK_STAT_PLAY.equals(trackStatMqVo.getStatType())){
                incrementField = "playStatNum";

            }else if(SystemConstant.TRACK_STAT_COMMENT.equals(trackStatMqVo.getStatType())){
                incrementField = "commentStatNum";
            }
            String finalIncrementField = incrementField;
            elasticsearchClient.update(
                    u -> u.index(INDEX_NAME).id(albumId.toString())
                            .script(s -> s.inline(i -> i.lang("painless").source("ctx._source."+ finalIncrementField +" += params.increment").params(Map.of("increment", JsonData.of(trackStatMqVo.getCount())))))
                    , AlbumInfoIndex.class
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
